#####################################################################
# Product:      KnowledgeEngine (Plone Edition)
#
# File:         SummaryReport.py
#
# Copyright:    Copyright (c) 2005, Faulkner Technologies
#
# Author:       Brian Oliver
#
# Description:  This report generates a collection of graphs, one
#               for each scorable (checkboxes, options, multiple 
#               choice questions and selections)
#
# See Also:     GenericObject, Component, Model, Section
#####################################################################

#####################################################################
## Knowledge Engine Library Imports
from Products.KnowledgeEngine.Common import *
from Products.KnowledgeEngine.Core import *
from Products.KnowledgeEngine.MultipleAssessmentReport import MultipleAssessmentReport, MultipleAssessmentReportInfo
from Products.KnowledgeEngine.PropertyDefinition import *
from Products.KnowledgeEngine.PropertyTypes import *
from Products.KnowledgeEngine.PropertyFormats import *
from Products.KnowledgeEngine.PropertyStructures import *
from Products.KnowledgeEngine.EventDefinition import *
from Products.KnowledgeEngine.WizardStepDefinition import *

#####################################################################
## Zope Library Imports
from Globals import DTMLFile
from App.ImageFile import ImageFile

#####################################################################
## Python Library Imports
import sys
import time

#####################################################################
## Python Logging Configuration
import logging
log = logging.getLogger("KnowledgeEngine.Reporting")

#####################################################################
## SummaryReportInfo Class Definition
class SummaryReportInfo(MultipleAssessmentReportInfo):
    """
        Defines the class info methods, defining information about
        the class without needing an instance of the class
    """
    
    #---------------------------------------------------------------#
    
    def getClassName(self):
        """
            Returns the name of the class for which 
            we are providing information.
        """
        return 'SummaryReport'
        
    #---------------------------------------------------------------#
    
    def getDisplayName(self):
        """
            Return a friendly name for the class instances
            (this is used for displaying in lists etc)
        """
    
        return "Summary Report"
        
    #---------------------------------------------------------------#
    
    def getIdentityType(self):
        """
            Return the identity type.  
        """
        
        return "SummaryReport"
        
    #---------------------------------------------------------------#
    
    def getClassIcon(self):
        """
            Return the appropriate icon file for the class.
        """
        
        return ImageFile('skins/SummaryReport_icon.gif', globals())
    
    #---------------------------------------------------------------#
    
    def onConfigureProperties(self):
        """
            Automatically called by the Knowledge Engine once
            the class has been loaded.  Use this method to register 
            class specific properties using the PropertyReflection interface.
            
            If this class is sub-classed, a call must be made to
            the super-class to ensure super-class properties are
            correctly registered
            
            NOTE: This method is called before ClassInfo.onAfterStartup()            
        """
        
        #initialise the super-class properties for reflection
        MultipleAssessmentReportInfo.onConfigureProperties(self)
    
    #---------------------------------------------------------------#
    
    def onConfigureEvents(self):
        """
            Automatically called by the Knowledge Engine once
            the class has been loaded.  Use this method to register 
            class specific events using the EventReflection interface.
            
            If this class is sub-classed, a call must be made to
            the super-class to ensure super-class properties are
            correctly registered
            
            NOTE: This method is called before ClassInfo.onAfterStartup()            
        """
                
        #initialise the super-class events for reflection
        MultipleAssessmentReportInfo.onConfigureEvents(self)
        
    #---------------------------------------------------------------#
    
#####################################################################
## SummaryReport Class Definition
class SummaryReport(MultipleAssessmentReport):
    """
        A class to represent a report consisting of graphs of
        component state
    """
    
    #---------------------------------------------------------------#
    
    #ABSTRACT TRANSIENT: a ClassInfo implementation instance to provide info about this class
    _CLASSINFO = SummaryReportInfo()
    
    #---------------------------------------------------------------#
    
    def __init__(self, id, identity):
        "Constructor for the object"        
        MultipleAssessmentReport.__init__(self, id, identity)
        
    #---------------------------------------------------------------#
    
    def renderReportFor(self, genericObject, request, wizardState, assessmentFilter, **args):
        """
            Renders the report content for the specified generic object
            and the type of report
        """
        
        #add the specified genericObject to the request
        request['GENERICOBJECT'] = genericObject
        
        #dynamically determine the report page template for the specified object
        return eval("self.%s_%s(self, REQUEST=request, **args)" % (genericObject.getClassInfo().getClassName(), self.getClassInfo().getClassName()))
        
    #---------------------------------------------------------------#
        
    def getWizardStepDefinitions(self, context=None):
        """
            ABSTRACT: This method is used by this class to
            determine the actual wizard step definitions
            for the wizard
            
            RETURN: [WizardStepDefinition]
        """
        
        #a function to determine if an object should be displayed in the tree 
        isDisplayableFunction = lambda genericObject: genericObject.isInstanceOf("Component") or genericObject.isInstanceOf("Model")
        
        #a function to determine if an object can be selected in the tree 
        isSelectableFunction = lambda genericObject: genericObject.isInstanceOf("CheckBox") or \
                                                     genericObject.isInstanceOf("MultipleChoice") or \
                                                     genericObject.isInstanceOf("Selection") or \
                                                     (genericObject.isContainer() and len(genericObject.getChildren()) > 0)
                     
        #a function to determine if an object should be expanded initially in a component tree
        isInitiallyExpandedFunction = lambda genericObject: genericObject.isInstanceOf("Model")
                
        wizardStep1Definition = WizardStepDefinition("Components for Summary Report", "Choose the component that you would like to appear in your summary report.")
        wizardStep1Definition.addPropertyDefinition(PropertyDefinition(identity="components", displayName="Assessment Components For Summary Report", defaultValue=[], description="Please select the components for which you would like to export assessment values.", propertyType=pt_NATIVE, propertyStructure=ps_LIST, propertyFormat=pf_GENERIC_OBJECT_CHOOSER, isSelectableFunction=isSelectableFunction, isDisplayableFunction=isDisplayableFunction, isInitiallyExpandedFunction=isInitiallyExpandedFunction, containerSelectionSelectsAll=true))

        return [wizardStep1Definition]

    #---------------------------------------------------------------#

    def renderReport(self, request, wizardState, **args):
        """
            Renders the report content (called by Report.generate)
            
            NOTE: Override this method to perform specific
            report generation/rendering using the provided
            parameters
        """

        starttime = time.clock()
        log.debug("%s:(%d): %s" % (sys._getframe().f_code.co_name, sys._getframe().f_lineno, "[commenced]: Generating Summary Report"))        

        #get the assessment storage service... we'll need that to manage the assessment
        assessmentStorageService = getAssessmentStorageService(self)

        #get the current user that is rendering the report
        user = request["USER"]
    
        #get the session from the request
        session = request["SESSION"]

        #get the response from the request (weird I know, but that's where it lives in Zope)
        response = request["RESPONSE"]

        #remove the model from the selected components (if it has been selected)
        components = wizardState.getPropertyValue("components")
        components = [component for component in components if component != self.getModel().getIdentity()]

        #order the components as they appear in the model
        components = [genericObject.getIdentity() for genericObject in self.getModel().getDescendantsByOrder() if genericObject.getIdentity() in components]

        #replace the components
        wizardState.setPropertyValue("components", components)

        #get the assessment filter to use to filter out assessments from the session 
        #(this is typically set by the assessment explorer)
        assessmentFilter = wizardState.getPropertyValue("assessmentFilter")
        
        #determine the number of assessments that satisfy the filter
        nrAssessments = assessmentStorageService.getNrAssessmentsSatisfyingFilter(assessmentFilter, user)   
        wizardState.setPropertyValue("nrAssessments", nrAssessments)
        request.set('NRASSESSMENTS', nrAssessments)
        
        #generate the report
        report = MultipleAssessmentReport.renderReport(self, request, wizardState, **args)

        endtime = time.clock()
        log.debug("%s:(%d): %s" % (sys._getframe().f_code.co_name, sys._getframe().f_lineno, "Report Generation Time: %f" % (endtime-starttime)))        
        log.debug("%s:(%d): %s" % (sys._getframe().f_code.co_name, sys._getframe().f_lineno, "[completed]: Generating Summary Report"))        
        
        return report
        
    #---------------------------------------------------------------#

#####################################################################
## Class Initialisation
# Register Class Information with the Knowledge Engine
registerGenericObjectClass(SummaryReport)
#####################################################################
